
perf = typeof perf == "undefined" ? {} : perf ;
perf.ui = {};

perf.ui.UI = function() {
  this.init();
};
perf.ui.UI.prototype = {
  
  init : function() {
     var self = this; 
     perf.utils.observe( document.getElementById( "run" ), "click", function(){
        self.run();
     });
     
     // テスト一覧
     var tests = perf.tests.tests;
     var str = "";
     for ( var i=0,n=tests.length;i<n;i++ ) {
       var id = perf.ui.utils.escape(tests[i].id);
         str += "<div class='test'>" +
            "  <div class='name middle'>" +
            "    <input type='checkbox' checked='checked' id='enable_" + id + "' " + 
            "       onClick='javascript:perf.tests.setEnable(\"" + id + "\", this.checked);' /'>" +
            "    <span id='name_"+  id +"'><label for='enable_" + id + "'>" + id + "</label></span>" +
            "  </div>" +
            "  <div class='result' id='result_" + id + "'></div>" +
            "</div>";
     }
     document.getElementById( "tests" ).innerHTML = str;
     
     this.console = document.getElementById( "console" );
  },
  run : function() {
    try {
      this.console.innerHTML = "";
      document.getElementById("execute").style.display = "block";
      document.getElementById("results").style.display = "none";
      
      var iteration = document.getElementById("control_iteration").value;
      var cooldown = document.getElementById("control_cooldown").value;
      var timeout = document.getElementById("control_timeout").value;
      var stopPerTest = document.getElementById("control_stopPerTest").checked;
      
      iteration = this.checkAsNumber( iteration, "試行回数", 1 );
      cooldown = this.checkAsNumber( cooldown, "実行間隔", 0 );
      timeout = this.checkAsNumber( timeout, "タイムアウト", 0 );
      
      var r = new perf.Runner( perf.tests.getTests(), 
          iteration, cooldown, timeout, stopPerTest );
      r.addListener( "runnerStarted", this );
      r.addListener( "runnerEnded", this );
      r.addListener( "testStarted", this );
      r.addListener( "testEnded", this );
      r.addListener( "stepStarted", this );
      r.addListener( "stepEnded", this );
      r.addListener( "logged", this );
      r.start();
    } catch ( msg ) {
      alert(msg);
    }
  },
  checkAsNumber : function( value, label, min ) {
    var n = parseInt(value, 10);
    if (isNaN(n)) throw label + "の値が不正です。";
    if (n<min) throw label + "の値が小さすぎます。";
    return n;
  },
  showResults : function ( results, tests ) {
    document.getElementById("execute").style.display = "none";
    var resultsElm = document.getElementById("results");
    resultsElm.style.display = "block";
    
    var summary = {};
    var str = "";
    for ( var i=0,n=tests.length;i<n;i++ ) {
      var id = perf.ui.utils.escape(tests[i].id);
      var res = results[tests[i].id];
      str += "<div class='panel'><div class='header' id='result_detail_" + id + "'><h3>" + id + "</h3></div>"
      summary[id] = {sum:0,raws:[]};
      for ( var j=0,m=res.length;j<m;j++ ) {
        str += this._createIterationResult( j, res[j]  ) + "</div>";
        if ( !res[j].logs["test"] || !res[j].logs["test"].end || !res[j].logs["test"].start )  continue;
        var time = res[j].logs["test"].end.time - res[j].logs["test"].start.time;
        if ( j === 0 ) {
          summary[id].testDrive = time;
        } else {
          summary[id].max = !summary[id].max || summary[id].max < time ? time :  summary[id].max;
          summary[id].min = !summary[id].min || summary[id].min > time ? time :  summary[id].min;
          summary[id].sum += time;
          summary[id].raws.push("<span class='status_" + perf.ui.utils.escape(res[j].status) + "'>" + perf.ui.utils.createTimeString(time) + "</span>");
        } 
      }
      summary[id].avg =  Math.floor( summary[id].sum / (res.length-1) );
    }
    resultsElm.innerHTML = this._createSummary(summary,tests) +  str;
  },
  _createSummary : function( summary, tests ) {
    var str = "<div class='panel'><div class='header'><h3>サマリ</h3></div>"
    str += "<table class='summary'>";
    str += "<tr><th>id</th><th>avg</th><th>max</th><th>min</th><th>test-drive</th><th>raws</th></tr>";
    for ( var i=0,n=tests.length;i<n;i++ ) {
      var id = perf.ui.utils.escape(tests[i].id);
      var s = summary[id];
      str += "<tr><td><a href='#result_detail_" + id + "'>" + id + "</a></td>" +
      		"<td class='time'>" + perf.ui.utils.createTimeString( s.avg ) + "</td>" +
      		"<td class='time'>" + perf.ui.utils.createTimeString( s.max ) + "</td>" +
      	  "<td class='time'>" +  perf.ui.utils.createTimeString( s.min ) + "</td>" +
      	  "<td class='time'>" +  perf.ui.utils.createTimeString( s.testDrive ) + "</td>" +
      		"<td>" + s.raws.join(", ") + "</td></tr>";
    }
    return str + "</table></div>";
  },
  _createIterationResult : function(iteration, result) {
    
    var start = result.logs.test ? result.logs.test.start : null;
    
    var tc = new perf.ui.TimeCalculator( result.logs );
    var str = "<div class='result'>";
    str += "<div class='iteration'><span class='iteration'>" + ( iteration === 0 ? "test-drive" : ""+perf.ui.utils.escape(iteration)+ "回目") + "</span>" +
    		"<span class='separator'>|</span>" + tc.diff( "test", "end" );
    str += "<span class='separator'>|</span><span class='status_" + perf.ui.utils.escape(result.status) + "'>" + perf.ui.utils.escape(result.status) + "</span></div>";
    
    for ( var i in result.logs ) {
      str += "<div class='step'>[" + perf.ui.utils.escape(i) + "]</div>";
      var logs =  result.logs[i];
      var sorted = [];
      for ( var j in logs) {
        if ( "start" === j || "end" === j ) continue;
        sorted.push( logs[j] );
      }
      sorted.sort( function(a,b) { return a.time - b.time; } )
      
      str += "<ul>";
      if (start) str += "<li class='lv1'>start : " + tc.start(i) + "</li>"
      for ( var j=0,m=sorted.length;j<m;j++ ) {
        str += "<li class='lv2'>" + perf.ui.utils.escape(sorted[j].event) + " : + " + tc.diff(i, sorted[j].event) + "</li>"
      }
      if (logs.end) str += "<li class='lv1'>end : + " + tc.diff(i, "end") + "</li>"
      str += "</ul></div>"
    }
    return str;
  },
  
  onRunnerStarted : function(ev){
    var tests = ev.tests;
    for ( var i=0,n=tests.length;i<n;i++ ) {
      document.getElementById("result_" + tests[i].id).innerHTML = ""; 
    }
  },
  onRunnerEnded : function(ev){
    this.showResults(ev.results, ev.tests );
  },
  onTestStarted : function(ev){
    var iterationStr = this._createIterationString(ev.iteration);
    this.console.innerHTML += "<div class='lv1'>[start " + perf.ui.utils.escape(ev.test.id) + " : " + iterationStr + "]</div>";
    document.getElementById("result_" + ev.test.id).innerHTML += 
        "<div class='middle'>" + iterationStr +  ": <span id='result_" + perf.ui.utils.escape(ev.test.id + "_" + ev.iteration) + "'>running...</span></div>";
  },
  onTestEnded : function(ev){
    this.console.innerHTML += "<div class='lv1'>[end " + perf.ui.utils.escape(ev.test.id) + " : " + this._createIterationString(ev.iteration) + "]</div><br/>";
    var result = ev.result;
    var tc = new perf.ui.TimeCalculator( result.logs );
    document.getElementById("result_" + ev.test.id + "_" + ev.iteration).innerHTML = 
      tc.diff( "test", "end" ) + "<span class='separator'>|</span><span class='status_" + 
      perf.ui.utils.escape(result.status) + "'>" + perf.ui.utils.escape(result.status) + "</span>";
  },
  onStepStarted : function(ev){
    this.console.innerHTML += "<div class='lv2'>[start " + perf.ui.utils.escape(ev.step) + "]</div>";
    document.getElementById("result_" + ev.test.id + "_" + ev.iteration).innerHTML = "running " + perf.ui.utils.escape(ev.step);
  },
  onStepEnded : function(ev){
    this.console.innerHTML += "<div class='lv2'>[end " + perf.ui.utils.escape(ev.step) + "]</div>";
  },
  onLogged : function(ev){
    this.console.innerHTML += "<div class='lv3'>" + perf.ui.utils.escape(ev.event) + "</div>";
  },
  _createIterationString : function( iteration ) {
    return iteration === 0 ? "test-drive" : perf.ui.utils.escape(""+iteration);
  }
};
perf.ui.utils = {
  createTimeString : function( time ) {
    if (!time) return "--";
    if (isNaN(time)) return "--";
    return (time  / 1000).toFixed(3) + "s";
  },
  escape : function( str ) {
    if ( !str ) return "";
    return (""+str).replace( /[&<>\"\']/g, function(c) {
      switch( c ) {
        case '&' : return '&amp;';
        case '>' : return '&gt;';
        case '<' : return '&lt;';
        case '"' : return '&quot;';
        case '\'' : return '&#x27;';
      }
    });
  }
}

perf.ui.TimeCalculator = function(logs) {
  this.logs = logs;
};
perf.ui.TimeCalculator.prototype = {
  start : function(step) {
    return this.logs[step] && this.logs[step].start ? new Date(this.logs[step].start.time) : "";
  },
  diff : function(step, name) {
    return this.logs[step] && this.logs[step].start && this.logs[step][name] ? 
        perf.ui.utils.createTimeString( this.logs[step][name].time - this.logs[step].start.time ) : "--";
  }
}
